package com.atjinggu.jinggumail.seckill.service.impl;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atjinggu.jinggumail.common.to.mq.SeckillOrderTo;
import com.atjinggu.jinggumail.common.utils.R;
import com.atjinggu.jinggumail.common.vo.MemberResponseVo;
import com.atjinggu.jinggumail.seckill.feign.CouponFeignService;
import com.atjinggu.jinggumail.seckill.feign.ProductFeignService;
import com.atjinggu.jinggumail.seckill.interceptor.LoginInterceptor;
import com.atjinggu.jinggumail.seckill.service.SeckillService;
import com.atjinggu.jinggumail.seckill.vo.*;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author jingming
 * @Type SeckillServiceImpl.java
 * @Desc
 * @date 2021/3/23 11:52
 */

@Slf4j
@Service
public class SeckillServiceImpl implements SeckillService {

    private final String SECKILL_CACHE_SKUS = "seckill:skus";

    private final String SECKILL_CACHE_SESSIONS ="seckill:sessions";

    private final String SECKILL_CACHE_STOCK ="seckill:stock";

    @Autowired
    CouponFeignService couponFeignService;

    @Autowired
    ProductFeignService productFeignService;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    RabbitTemplate rabbitTemplate;



    @Override
    public void list3DaysSeckillSku() {
        R r = couponFeignService.list3DaysSeckillSku();
        if(r.getCode() == 0){
            List<SeckillSessionVo> sessions = r.getData(new TypeReference<List<SeckillSessionVo>>() {
            });

            //1.保存秒杀的商品信息
            saveSeckillSkus(sessions);

            //2.保存秒杀商品的基本信息
            saveSeckKillSkuInfos(sessions);


        }

    }

    public List<SeckillRedisTo> blockSeckill(BlockException e) {
        log.error("被熔断了！");
        return null;
    }

    @Override
    @SentinelResource(value = "getCurrentSeckill",blockHandler = "blockSeckill")
    public List<SeckillRedisTo> getCurrentSeckill() {
        try(Entry entry =  SphU.entry("Seckill")){
            long time = new Date().getTime();
            Set<String> keys = redisTemplate.keys(SECKILL_CACHE_SESSIONS + "*");
            for (String key : keys) {
                String replace = key.replace(SECKILL_CACHE_SESSIONS, "");
                String[] s = replace.split("_");
                long start = Long.parseLong(s[0]);
                long end = Long.parseLong(s[1]);
                if(time>=start && time<=end){
                    List<String> range = redisTemplate.opsForList().range(key, -100, 100);
                    if(CollectionUtils.isNotEmpty(range)) {
                        BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SECKILL_CACHE_SKUS);
                        ops.expire(1,TimeUnit.SECONDS);  //设置过期
                        ops.put("key","value"); //添加
                        ops.get("key");    //获取
                        List<String> strings = ops.multiGet(range);  //批量获取遍历
                        List<SeckillRedisTo> collect = strings.stream().map(item -> {
                            SeckillRedisTo to = JSON.parseObject(item, SeckillRedisTo.class);
                            return to;
                        }).collect(Collectors.toList());
                        return collect;
                    }
                    break;
                }
            }
        }
        catch (BlockException e){
            log.error("流量被限制，"+e.getMessage());
        }


        return null;
    }

    @Override
    public SeckillRedisTo getSeckkillInfo(String skuId) {
        BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SECKILL_CACHE_SKUS);
        Set<String> keys = ops.keys();
        String grep = "\\d_"+skuId;
        if(CollectionUtils.isNotEmpty(keys)) {
            for (String key : keys) {
                if(Pattern.matches(grep, key)){
                    String s = ops.get(key);
                    SeckillRedisTo to = JSON.parseObject(s, SeckillRedisTo.class);
                    long start = to.getStartTime().getTime();
                    long end = to.getEndTime().getTime();
                    long  now = new Date().getTime();
                    if(now >= start && now <= end){

                    }
                    else{
                        to.setRandomKey(null);
                    }
                    return to;
                }
            }
        }
        return null;
    }

    @Override
    public String killSku(String id, String code, Integer num) {
        MemberResponseVo vo = LoginInterceptor.loginUser.get();
        BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SECKILL_CACHE_SKUS);

        String info = ops.get(id);
        SeckillRedisTo to = JSON.parseObject(info, SeckillRedisTo.class);
        long start = to.getStartTime().getTime();
        long end = to.getEndTime().getTime();
        //1.校验时间合法性
        long now = new Date().getTime();
        if(now < start || now > end){
            return null;
        }

        //校验随机码和商品id
        String randomKey = to.getRandomKey();
        String skuId= to.getPromotionSessionId()+"_"+to.getSkuId();
        if(!StringUtils.equals(code,randomKey) || !StringUtils.equals(skuId,id)){
            return null;
        }

        //校验购物数量
        if(num > to.getSeckillLimit().intValue()){
            return null;
        }

        //校验这个人是否已经购买过了
        String key = vo.getId() + "_"+to.getPromotionSessionId()+"_"+to.getSkuId();
        Boolean absent = redisTemplate.opsForValue().setIfAbsent(key, num.toString(), end-now, TimeUnit.MILLISECONDS);
        if(!absent){
            return null;
        }

        //扣减信号量
        RSemaphore semaphore = redissonClient.getSemaphore(SECKILL_CACHE_STOCK + code);
        try {
            boolean b = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
            if(b){
                //生成一个订单id
                String timeId = IdWorker.getTimeId();
                SeckillOrderTo orderTo = new SeckillOrderTo();
                orderTo.setMemberId(vo.getId());
                orderTo.setNum(num);
                orderTo.setOrderSn(timeId);
                orderTo.setPromotionSessionId(to.getPromotionSessionId());
                orderTo.setSeckillPrice(to.getSeckillPrice());
                rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",orderTo);
                return timeId;
            }
        } catch (InterruptedException e) {
            return null;
        }

        return null;
    }

    private void saveSemaphore( SeckillRedisTo to) {
        RSemaphore semaphore = redissonClient.getSemaphore(SECKILL_CACHE_STOCK + to.getRandomKey());
        semaphore.trySetPermits(to.getSeckillCount().intValue());
    }

    private void saveSeckKillSkuInfos(List<SeckillSessionVo> sessions) {
        BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(SECKILL_CACHE_SKUS);
        sessions.stream().forEach(session -> {
            List<SeckillSkuRelation> relations = session.getRelations();
            relations.stream().forEach(r -> {
                Long skuId = r.getSkuId();
                R skuInfo = productFeignService.getSkuInfo(skuId);
                if(skuInfo.getCode() == 0){
                        SkuInfoVo info = skuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {
                        });
                    String key =  r.getPromotionSessionId().toString() + "_" + info.getSkuId().toString();
                    if(!ops.hasKey(key)) {
                        SeckillRedisTo to = new SeckillRedisTo();
                        to.setSkuInfoVo(info);
                        BeanUtils.copyProperties(r, to);
                        to.setRandomKey(UUID.randomUUID().toString().replace("-", ""));
                        to.setStartTime(session.getStartTime());
                        to.setEndTime(session.getEndTime());
                        String s = JSON.toJSONString(to);
                        ops.put(r.getPromotionSessionId().toString() + "_" + info.getSkuId().toString(), s);
                        saveSemaphore(to);
                    }
                }
            });
        });
    }

    private void saveSeckillSkus(List<SeckillSessionVo> sessions) {
        sessions.stream().forEach(item -> {
            long start = item.getStartTime().getTime();
            long end = item.getEndTime().getTime();
            String key = SECKILL_CACHE_SESSIONS+start+"_"+end+"_"+item.getId();
            if(!redisTemplate.hasKey(key)) {
                List<SeckillSkuRelation> relations = item.getRelations();
                if (CollectionUtils.isNotEmpty(relations)) {
                    List<String> collect = relations.stream().map(r -> r.getPromotionSessionId().toString()+"_"+r.getSkuId().toString()).collect(Collectors.toList());
                    redisTemplate.opsForList().leftPushAll(key, collect);
                }
            }
        });
    }
}
